﻿#region Usings

using System;
using System.Windows;
using System.Windows.Media;
using Box2DX.Common;
using Box2DX.Dynamics;
using SilverBox.Helpers;
using Math=System.Math;

#endregion

namespace SilverBox.Physics
{
    public class Entity
    {
        public static readonly DependencyProperty ElementEntityProperty =
            DependencyProperty.RegisterAttached("ElementEntity", typeof (Entity), typeof (Entity),
                                                new PropertyMetadata(null));

        private Body body;
        private IBodyMover bodyMover;
        private BodyParameters bodyParameters;
        public EventHandler BoundaryViolation;
        public EventHandler<CollideEventArgs> Collide;
        private PhysicsContext context;
        private FrameworkElement element;
        private FrameworkElement shape;
        private Point worldOffset;

        public Entity(FrameworkElement element)
        {
            if (GetElementEntity(element) != null)
                throw new ArgumentException("Element already has an entity defined.");

            SetElementEntity(element, this);

            this.element = element;
        }


        public Simulator Simulator
        {
            get { return context.Simulator; }
        }

        public Body Body
        {
            get { return body; }
        }

        public object UserData { get; set; }

        public FrameworkElement Element
        {
            get { return element; }
        }

        public FrameworkElement Shape
        {
            get { return shape; }
        }


        public Vector2 BodySize
        {
            get { return Simulator.SimulatorContext.ScreenSizeToWorldSize(ElementSize); }
        }

        public Vector2 BodyPosition
        {
            get { return Body.Position; }
        }

        public double BodyAngle
        {
            get { return Body.Angle; }
        }

        public Size ElementSize
        {
            get { return Shape.RenderSize; }
        }

        public Point ElementPosition
        {
            get { return bodyMover.ElementPosition.Add(worldOffset); }
            set
            {
                value = value.Sub(worldOffset);
                bodyMover.ElementPosition = value;
            }
        }

        public Point ElementCenter
        {
            get { return ElementPosition.Add(ElementSize.Mul(0.5)); }
        }

        public double ElementAngle
        {
            get { return bodyMover.ElementAngle/180*Math.PI; }
            set
            {
                if (bodyParameters.CanRotate)
                {
                    bodyMover.ElementAngle = value/Math.PI*180;
                }
            }
        }

        public void Initialize(PhysicsContext context, BodyParameters bodyParameters)
        {
            this.context = context;
            this.bodyParameters = bodyParameters;

            var shapeProvider = element as IShapeProvider;
            shape = element;
            if (shapeProvider != null)
                shape = shapeProvider.BodyShape;

            if (shape != null)
            {
                bodyMover = element as IBodyMover;
                if (bodyMover == null)
                    bodyMover = new BodyMover(element);

                var worldTransform = (Transform) shape.TransformToVisual(this.context.Host);
                var worldMatrix = (MatrixTransform) worldTransform;


                worldOffset = new Point(worldMatrix.Matrix.OffsetX, worldMatrix.Matrix.OffsetY);


                bodyParameters.UserData = this;

                body = BodyHelper.GetBodyForElement(Simulator, this, bodyParameters);
            }
        }

        public void OnCollide(ContactPoint point)
        {
            if (Collide != null)
            {
                var a = (Entity) point.Shape1.UserData;
                var b = (Entity) point.Shape2.UserData;

                if (a == this)
                    Collide(this, new CollideEventArgs(point, a, b));
                else
                    Collide(this, new CollideEventArgs(point, b, a));
            }
        }

        public void OnBoundaryViolation()
        {
            if (BoundaryViolation != null)
                BoundaryViolation(this, EventArgs.Empty);
        }

        public static Entity GetElementEntity(FrameworkElement element)
        {
            return (Entity) element.GetValue(ElementEntityProperty);
        }

        public static void SetElementEntity(FrameworkElement element, Entity entity)
        {
            element.SetValue(ElementEntityProperty, entity);
        }
    }

    public class CollideEventArgs : EventArgs
    {
        public CollideEventArgs(ContactPoint contactPoint, Entity self, Entity other)
        {
            ContactPoint = contactPoint;

            Self = self;
            Other = other;
        }

        public ContactPoint ContactPoint { get; private set; }

        public Entity Self { get; private set; }
        public Entity Other { get; private set; }
    }
}